import GameModel
import copy
import numpy as np
import random
import time

weight_table = {
    1: 999999, -1: -999999,
    2: 2, -2: -2,
    3: 2, -3: -2,
    4: 4, -4: -4,
    5: 12, -5: -12,
    6: 5, -6: -5,
    7: 1, -7: -1
}


class Game_MinMaxNode:
    def __init__(self, node_type, parent_node,
                 chess_piece_board: np.ndarray, chess_piece_table: dict, _layer: int):
        self.type = node_type
        self.p_node = parent_node
        self.alpha_beta_score = None
        self.cp_table = chess_piece_table
        self.cp_board = chess_piece_board
        self.layer = _layer

        self.calculate_node_times = 1

    def get_evaluateScore(self) -> int:
        if self.layer > 0:
            pies = (5, 6, 4, 7, 3, 2, 1) if self.type == 1 else (-5, -6, -4, -7, -3, -2, -1)
            for pie in pies:
                for pie_pos in np.array(self.cp_table[pie]):
                    all_decisions = self.create_base_decision(pie_pos, self.cp_board)
                    for decision in all_decisions:
                        # 子节点新棋局
                        new_board, new_table = Game_MinMaxNode.create_next_board(pie_pos, decision, self.cp_board,
                                                                                 self.cp_table)
                        # 添加子节点(子节点的类型为父节点相反的类型)
                        child_node = Game_MinMaxNode(
                            node_type=self.type * -1,
                            parent_node=self,
                            chess_piece_board=new_board,
                            chess_piece_table=new_table,
                            _layer=self.layer - 1
                        )
                        # 获取子节点分数
                        new_score = child_node.get_evaluateScore()

                        # 获得子节点数
                        self.calculate_node_times += child_node.calculate_node_times

                        # self.alpha_beta_score = self.min_max(new_score)

                        # α-β剪枝
                        if self.alpha_beta_pruning(new_score):
                            self.alpha_beta_score = new_score

                            # 自身更新的 α-β 值 若不符合父节点的值(自身存在父节点的情况下) 自身需要被剪枝
                            if self.p_node is not None and not self.p_node.alpha_beta_pruning(self.alpha_beta_score):
                                # 返回的值本身不符合父节点的更新值的条件 本身无效 但函数格式需要返回一个值
                                return self.alpha_beta_score
        # 底层节点只需要直接计算分数即可
        else:
            self.alpha_beta_score = Game_MinMaxNode.create_gameScore(self.cp_table)
        # 返回该节点的确定分数值
        return self.alpha_beta_score

    def min_max(self, current_val):
        if self.alpha_beta_score is None:
            return current_val
        if self.type == 1:
            return max(current_val, self.alpha_beta_score)
        else:
            return min(current_val, self.alpha_beta_score)

    # α-β剪枝
    def alpha_beta_pruning(self, current_val):
        if self.alpha_beta_score is None:
            return True
        if self.type == 1:
            return True if current_val > self.alpha_beta_score else False
        else:
            return True if current_val < self.alpha_beta_score else False

    # 评估函数
    @staticmethod
    def create_gameScore(table: dict):
        score = 0
        # 计算子力分
        for pie in table.keys():
            score += weight_table[pie] * len(table[pie]) + (0.05 - random.random() * 0.1)

        return score

    # 生成可用决策
    @staticmethod
    def create_base_decision(position: np.ndarray, board: np.ndarray):
        """
        生成棋子的基础决策
        :param position: 棋子的位置
        :param board: 棋盘的导入形式
        :return: 棋子的移动点
        """
        chess_pie = board[position[0]][position[1]]

        if chess_pie == 0:
            return np.array([])

        valid_moves = []

        if chess_pie == 1 or chess_pie == -1:
            # 将帅的基本走法
            mt = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]])
            for move in mt:
                new_pos = position + move
                if ((0 <= new_pos[0] <= 2 or 7 <= new_pos[0] <= 9) and 3 <= new_pos[1] <= 5) and \
                        (board[new_pos[0], new_pos[1]] * chess_pie <= 0):
                    valid_moves.append(new_pos)

                if move[0] != 0:
                    for step in range(1, 10):
                        new_pos = position + np.array([move[0] * step, move[1] * step])
                        if not (0 <= new_pos[0] < 10 and 0 <= new_pos[1] < 9):
                            break
                        if board[new_pos[0], new_pos[1]] * chess_pie == 0:
                            continue
                        if board[new_pos[0], new_pos[1]] * chess_pie == -1:
                            valid_moves.append(new_pos)
                            break
                        else:
                            break

        elif chess_pie == 2 or chess_pie == -2:
            # 仕士的基本走法
            mt = np.array([[1, 1], [-1, -1], [1, -1], [-1, 1]])
            for move in mt:
                new_pos = position + move
                if ((0 <= new_pos[0] <= 2 or 7 <= new_pos[0] <= 9) and 3 <= new_pos[1] <= 5) and \
                        (board[new_pos[0], new_pos[1]] * chess_pie <= 0):
                    valid_moves.append(new_pos)

        elif chess_pie == 3 or chess_pie == -3:
            # 象的基本走法
            mt = np.array([[2, 2], [-2, -2], [2, -2], [-2, 2]])
            for move in mt:
                new_pos = position + move
                if (0 <= new_pos[0] <= 4 if chess_pie == 3 else 5 <= new_pos[0] <= 9) and \
                        (0 <= new_pos[1] <= 8) and \
                        (board[position[0] + int(move[0] / 2)][
                             position[1] + int(move[1] / 2)] == 0) and \
                        board[new_pos[0], new_pos[1]] * chess_pie <= 0:
                    valid_moves.append(new_pos)

        elif chess_pie == 4 or chess_pie == -4:
            # 马的基本走法
            mt = np.array([[1, 0], [-1, 0], [0, 1], [0, -1]])
            for move in mt:
                # 判断是否蹩马腿
                horse_leg = position + move
                if 9 >= horse_leg[0] >= 0 and \
                        8 >= horse_leg[1] >= 0 == board[horse_leg[0], horse_leg[1]]:
                    # 如果不蹩马腿 则先生成最基础的落点
                    new_pos1 = horse_leg + move
                    new_pos2 = horse_leg + move
                    if move[0] != 0:
                        new_pos1[1] += 1
                        new_pos2[1] -= 1
                    else:
                        new_pos1[0] += 1
                        new_pos2[0] -= 1
                    # 最后再排除越界 己方棋子占格和双王见面的情况
                    if (0 <= new_pos1[0] <= 9 and 0 <= new_pos1[1] <= 8) and \
                            (board[new_pos1[0], new_pos1[1]] * chess_pie <= 0):
                        valid_moves.append(new_pos1)

                    if (0 <= new_pos2[0] <= 9 and 0 <= new_pos2[1] <= 8) and \
                            (board[new_pos2[0], new_pos2[1]] * chess_pie <= 0):
                        valid_moves.append(new_pos2)

        elif chess_pie == 5 or chess_pie == -5:
            # 车的基本走法
            directions = np.array([[1, 0], [-1, 0], [0, 1], [0, -1]])
            for direction in directions:
                for step in range(1, 10):  # 车可以走的最大步数
                    new_pos = position + np.array([direction[0] * step, direction[1] * step])
                    if not (0 <= new_pos[0] < 10 and 0 <= new_pos[1] < 9):
                        break
                    if board[new_pos[0], new_pos[1]] * chess_pie <= 0:
                        valid_moves.append(new_pos)
                        if board[new_pos[0], new_pos[1]] * chess_pie < 0:
                            break
                    else:
                        break

        elif chess_pie == 6 or chess_pie == -6:
            # 炮的基本走法
            directions = np.array([[1, 0], [-1, 0], [0, 1], [0, -1]])
            for direction in directions:
                # 是否遇到炮台
                is_fort_barbette = False
                for step in range(1, 10):
                    new_pos = position + np.array([direction[0] * step, direction[1] * step])
                    if not (0 <= new_pos[0] < 10 and 0 <= new_pos[1] < 9):
                        break
                    # 第一个遇到的子作为炮台
                    if not is_fort_barbette and board[new_pos[0], new_pos[1]] != 0:
                        is_fort_barbette = True
                        continue

                    # 分别处理是否有炮台的情况
                    # 无炮台的情况
                    if not is_fort_barbette:
                        valid_moves.append(new_pos)
                    # 有炮台的情况
                    else:
                        # 存在炮台时遇到的第一个对方棋子可以是炮的落点
                        if board[new_pos[0], new_pos[1]] * chess_pie < 0:
                            valid_moves.append(new_pos)
                            break
                        # 两个炮架无落点
                        elif board[new_pos[0], new_pos[1]] * chess_pie > 0:
                            break

        elif chess_pie == 7 or chess_pie == -7:
            # 兵卒的基本走法
            mt = np.array([[int(chess_pie / 7), 0], [0, 1], [0, -1]])
            for move in mt:
                new_pos = position + move
                if not (0 <= new_pos[0] < 10 and 0 <= new_pos[1] < 9):
                    continue

                if position[0] <= 4 if chess_pie == 7 else 5 <= position[0]:
                    if board[new_pos[0]][new_pos[1]] * chess_pie <= 0:
                        valid_moves.append(new_pos)
                    break

                elif board[new_pos[0]][new_pos[1]] * chess_pie <= 0:
                    valid_moves.append(new_pos)

        return valid_moves

    # 生成该决策改变的新棋局
    @staticmethod
    def create_next_board(origin_pos: np.ndarray, aim_pos: np.ndarray, board: np.ndarray, table: dict):
        new_table = copy.deepcopy(table)
        new_board = board.copy()

        pie_1 = new_board[origin_pos[0]][origin_pos[1]]
        pie_2 = new_board[aim_pos[0]][aim_pos[1]]

        if pie_1 == 0:
            raise ValueError(f"The selection of origin_pos must be a point where there is a piece. got{origin_pos}")
        else:
            if pie_2 != 0:
                new_table[pie_2].remove([aim_pos[0], aim_pos[1]])
            new_table[pie_1].append([aim_pos[0], aim_pos[1]])
            new_table[pie_1].remove([origin_pos[0], origin_pos[1]])

            new_board[origin_pos[0]][origin_pos[1]] = 0
            new_board[aim_pos[0]][aim_pos[1]] = pie_1

        return new_board, new_table


class Chess_AI:
    def __init__(self, chess_game: GameModel.CN_Chess_Game):
        self.chess_game = chess_game

    def calculate_res(self, action_color):
        score = None
        res_pos = None
        # 计算红方分
        if action_color == -1:
            for pie in (-5, -6, -4, -7, -3, -2, -1):
                # 一定要转换为数组 不然 for 迭代器会出问题
                pie_all_movement_pos = np.array(self.chess_game.chess_position_table.get(pie))
                for pos in pie_all_movement_pos:
                    all_legal_movement_pos = self.chess_game.get_legal_movement_points(pos)
                    for movement_pos in all_legal_movement_pos:
                        new_board, new_table = Game_MinMaxNode.create_next_board(
                            pos, movement_pos,
                            self.chess_game.CN_chess_board,
                            self.chess_game.chess_position_table
                        )
                        decision_node = Game_MinMaxNode(
                            node_type=action_color,
                            parent_node=None,
                            chess_piece_board=new_board,
                            chess_piece_table=new_table,
                            _layer=1
                        )
                        new_score = decision_node.get_evaluateScore()

                        if score is not None and new_score < score:
                            score = new_score
                            res_pos = (pos, movement_pos)
                        elif score is None:
                            score = new_score
                            res_pos = (pos, movement_pos)
        # 计算黑方分
        elif action_color == 1:
            tick_1 = time.time()
            all_nodes_count = 0
            for pie in (5, 6, 4, 7, 3, 2, 1):
                # 一定要转换为数组 不然 for 迭代器会出问题
                pie_all_movement_pos = np.array(self.chess_game.chess_position_table.get(pie))
                for pos in pie_all_movement_pos:
                    all_legal_movement_pos = self.chess_game.get_legal_movement_points(pos)
                    for movement_pos in all_legal_movement_pos:
                        new_board, new_table = Game_MinMaxNode.create_next_board(
                            pos, movement_pos,
                            self.chess_game.CN_chess_board,
                            self.chess_game.chess_position_table
                        )
                        decision_node = Game_MinMaxNode(
                            node_type=-action_color,
                            parent_node=None,
                            chess_piece_board=new_board,
                            chess_piece_table=new_table,
                            _layer=3
                        )
                        new_score = decision_node.get_evaluateScore()
                        all_nodes_count += decision_node.calculate_node_times
                        # print(f"score: {decision_node.alpha_beta_score} \n decision_node {[pos, movement_pos]}")
                        if score is not None and new_score > score:
                            score = new_score
                            res_pos = (pos, movement_pos)
                        elif score is None:
                            score = new_score
                            res_pos = (pos, movement_pos)
            tick_2 = time.time()
            print(f"calculate time: {tick_2 - tick_1:.2f} node count: {all_nodes_count}")
        return res_pos
